//
//  CameraControllerswift
//  CameraDemo
//
//  Created by zzp on 2018/5/3.
//  Copyright © 2018年 zzp. All rights reserved.
//

import UIKit
import AVFoundation

enum CameraError: Error {
    case noVideoConnection
    case noImageCapture
    case crop
    case noMetaRect
    case noDevice
}

@available(iOS 10.0, *)
extension CameraController: AVCapturePhotoCaptureDelegate {
    
}

class CameraController: UIViewController {
    
    // camera button
    class CameraButton: UIButton {
        var pathLayer: CAShapeLayer!
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.setup()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            self.setup()
        }
        
        func setup() {
            pathLayer = CAShapeLayer()
            pathLayer.path = UIBezierPath(roundedRect: CGRect(x: 8, y: 8, width: 50, height: 50), cornerRadius: 25).cgPath
            pathLayer.strokeColor = nil
            pathLayer.fillColor = UIColor.white.cgColor
            self.layer.addSublayer(pathLayer)
        }
        
        override func draw(_ rect: CGRect) {
            let outerRing = UIBezierPath(ovalIn: CGRect(x: 3, y: 3, width: 60, height: 60))
            outerRing.lineWidth = 6
            UIColor.white.setStroke()
            outerRing.stroke()
        }
    }
    
    var modelView: UIView!
    var lineView: UIView?
    var navTitleLabel: UILabel?
    var titleLabel: UILabel?
    var subLabel: UILabel?
    
    var cameraView: UIView! {
        didSet {
            cameraView.layer.mask = cameraLayer
        }
    }
    
    lazy var cameraLayer: CALayer = {
        let layer = CALayer()
//        layer.backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.5).cgColor
        layer.addSublayer(rectLayer)
        return layer
    }()
    
    lazy var rectLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        layer.fillColor = UIColor.black.cgColor
        layer.strokeColor = UIColor.white.cgColor
        return layer
    }()
    
    var imageOrientation: UIImageOrientation {
        let orientation: UIDeviceOrientation = UIDevice.current.orientation
        switch orientation {
            case .portrait:
                return .right
            case .portraitUpsideDown:
                return .left
            case .landscapeRight:
                return .down
            case .landscapeLeft:
                return .up
            default:
                return .right
        }
    }
    
    var captureSession: AVCaptureSession!
    
    var photoOutput: AVCaptureStillImageOutput!
    var cameraPreviewLayer: AVCaptureVideoPreviewLayer!
    
    let buttonSize: CGFloat  = 66
    var cameraCompeletCompletion: ((UIImage) -> Void)?
    var rectWidth: CGFloat = 0
    var rectHeight: CGFloat = 0
    var options: CameraOptions = CameraOptions()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.7)
        self.view.isOpaque = false
        
        setupView()
        setupSession()
        do {
            try setupInput()
        } catch {
            print("error")
        }
        setPreviewLayer()
        startRunningCaptureSession()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        updatePreviewFrame()
        drawCameraRectView(width: options.imageWidth, height: options.imageHeight)
    }
    
    func setupView() {
        // create model view
        modelView = UIView()
        modelView.layer.cornerRadius = 10
        modelView.backgroundColor = UIColor.white
        self.view.addSubview(modelView)
        
        // creat line
        lineView = UIView()
        lineView?.backgroundColor = UIColor(red: 230/255, green: 233/255, blue: 237/255, alpha: 1)
        self.view.addSubview(lineView!)
        
        // create title label
        navTitleLabel = UILabel()
        navTitleLabel?.setLabel(sizeFont: 20, color: UIColor(red: 70/255, green: 74/255, blue: 84/255, alpha: 1))
        navTitleLabel?.text = options.navTitle
        modelView.addSubview(navTitleLabel!)
        
        titleLabel = UILabel()
        titleLabel?.setLabel(sizeFont: 16, color: UIColor(red: 70/255, green: 74/255, blue: 84/255, alpha: 1))
        titleLabel?.text = options.title
        titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
        modelView.addSubview(titleLabel!)
        
        subLabel = UILabel()
        subLabel?.setLabel(sizeFont: 12, color: UIColor(red: 170/255, green: 178/255, blue: 179/255, alpha: 1))
        subLabel?.text = options.subtitle
        modelView.addSubview(subLabel!)

        // create camera view
        cameraView = UIView(frame: view.frame)
        self.view.addSubview(cameraView)
        
        // create launch button
        let launchButton: UIButton = CameraButton(frame: CGRect(x: view.bounds.width / 2 - buttonSize / 2, y: view.bounds.height - 90, width: buttonSize, height: buttonSize))
        launchButton.addTarget(self, action: #selector(launchAction), for: .touchUpInside)
        self.view.addSubview(launchButton)
        
        setupAutoLayout()
    }
    
    func setupAutoLayout() {
        modelView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addConstraints([
            NSLayoutConstraint(item: modelView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 98),
            NSLayoutConstraint(item: modelView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1.0, constant: -98),
            NSLayoutConstraint(item: modelView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1.0, constant: 128),
            NSLayoutConstraint(item: modelView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1.0, constant: -128)
        ])
        
        // nav title constraint
        navTitleLabel?.translatesAutoresizingMaskIntoConstraints = false
        self.view.addConstraints([
            NSLayoutConstraint(item: navTitleLabel!, attribute: .top, relatedBy: .equal, toItem: modelView, attribute: .top, multiplier: 1.0, constant: 25),
            NSLayoutConstraint(item: navTitleLabel!, attribute: .centerX, relatedBy: .equal, toItem: modelView, attribute: .centerX, multiplier: 1.0, constant: 0)
        ])
        
        // line
        lineView?.translatesAutoresizingMaskIntoConstraints = false
        self.view.addConstraints([
            NSLayoutConstraint(item: lineView!, attribute: .top, relatedBy: .equal, toItem: navTitleLabel, attribute: .bottom, multiplier: 1.0, constant: 25),
            NSLayoutConstraint(item: lineView!, attribute: .width, relatedBy: .equal, toItem: modelView, attribute: .width, multiplier: 1.0, constant: 0),
            NSLayoutConstraint(item: lineView!, attribute: .left, relatedBy: .equal, toItem: modelView, attribute: .left, multiplier: 1.0, constant: 0),
            NSLayoutConstraint(item: lineView!, attribute: .height, relatedBy: .equal, toItem: modelView, attribute: .height, multiplier: 0.0, constant: 1)
            ])
        
        titleLabel?.translatesAutoresizingMaskIntoConstraints = false
        self.view.addConstraints([
            NSLayoutConstraint(item: titleLabel!, attribute: .top, relatedBy: .equal, toItem: lineView, attribute: .bottom, multiplier: 1.0, constant: 12),
            NSLayoutConstraint(item: titleLabel!, attribute: .centerX, relatedBy: .equal, toItem: modelView, attribute: .centerX, multiplier: 1.0, constant: 0)
            ])
        
        subLabel?.translatesAutoresizingMaskIntoConstraints = false
        self.view.addConstraints([
            NSLayoutConstraint(item: subLabel!, attribute: .top, relatedBy: .equal, toItem: titleLabel, attribute: .bottom, multiplier: 1.0, constant: 10),
            NSLayoutConstraint(item: subLabel!, attribute: .centerX, relatedBy: .equal, toItem: modelView, attribute: .centerX, multiplier: 1.0, constant: 0)
            ])
    }
    
    func updatePreviewFrame() {
        if cameraView != nil {
            self.cameraPreviewLayer?.frame = cameraView!.bounds
        }
    }
    
    func drawCameraRectView(width: CGFloat = 0, height: CGFloat = 0) {
        
        let cameraSize = self.cameraView.frame.size
        var frameHeight: CGFloat = height
        var frameWidth: CGFloat = width
        
        if frameWidth == 0 || frameHeight == 0 {
            let orientation: UIDeviceOrientation = UIDevice.current.orientation
            switch orientation {
            case .landscapeRight, .landscapeLeft:
                frameHeight = cameraSize.height / 1.4
                frameWidth = cameraSize.width / 1.5
            default:
                frameHeight = cameraSize.height / 1.5
                frameWidth = cameraSize.width / 1.2
            }
        }
        
        let x: CGFloat = (cameraSize.width - frameWidth) / 2
        let y: CGFloat = ((cameraSize.height - frameHeight) / 2)
        
        rectLayer.frame = CGRect(x: x, y: y + 50, width: frameWidth, height: frameHeight)
        
        let bezierPathFrame = CGRect(origin: .zero, size: rectLayer.frame.size)
        rectLayer.path = UIBezierPath(roundedRect: bezierPathFrame, cornerRadius: 10).cgPath
        cameraLayer.frame = cameraView.bounds
    }
    
    @objc func launchAction() {
        cropImage { (cImage) in
            guard let croppedImage = cImage else {
                return
            }
            self.cameraCompeletCompletion?(croppedImage)
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}


// crop image
extension CameraController {

    func cropImage(completion: @escaping (UIImage?) -> Void) {
        guard let videoConnection = photoOutput.connection(with: .video) else {
            completion(nil)
            return
        }
        photoOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (buffer, error) in
            guard let imageBuffer = buffer,
                let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageBuffer),
                let capturedImage: UIImage = UIImage(data: imageData) else {
                    completion(nil)
                    return;
            }
            do {
                let croppedImage = try self.crop(image: capturedImage, withRect: self.rectLayer.frame)
                completion(croppedImage)
            } catch {
                completion(nil)
            }
            
        })
        
    }
    
    func crop(image: UIImage, withRect rect: CGRect) throws -> UIImage {
        let originalSize: CGSize
        guard let metaRect = cameraPreviewLayer?.metadataOutputRectConverted(fromLayerRect: rect) else {
            throw CameraError.noMetaRect
        }
        
        if image.imageOrientation == UIImageOrientation.left
            || image.imageOrientation == UIImageOrientation.right {
            originalSize = CGSize(width: image.size.height,
                                  height: image.size.width)
        } else {
            originalSize = image.size
        }
        
        let x = metaRect.origin.x * originalSize.width
        let y = metaRect.origin.y * originalSize.height
        let cropRect: CGRect = CGRect( x: x,
                                       y: y,
                                       width: metaRect.size.width * originalSize.width,
                                       height: metaRect.size.height * originalSize.height).integral
        guard let cropedCGImage = image.cgImage?.cropping(to: cropRect) else {
            throw CameraError.crop
        }
        return UIImage(cgImage: cropedCGImage, scale: 1, orientation: imageOrientation).fixedOrientation()!
    }
}

// camera
extension CameraController {
    
    func setupSession() {
        captureSession = AVCaptureSession()
    }
    
    fileprivate func getDevice() throws -> AVCaptureDevice {
        if #available(iOS 10.0, *) {
            let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera], mediaType: .video, position: .back)
            guard let device = deviceDiscoverySession.devices.first(where: { device in
                device.position == .back
            }) else {
                throw CameraError.noDevice
            }
            return device
        } else {
            guard let device = AVCaptureDevice.devices(for: .video).first(where: { device in
                device.position == .back
            }) else {
                throw CameraError.noDevice
            }
            return device
        }
    }
    
    func setupInput() throws {
        let captureDevice: AVCaptureDevice = try getDevice()
        let captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice)
        if captureSession.canAddInput(captureDeviceInput) {
            captureSession.addInput(captureDeviceInput)
        }
        //Output
        photoOutput = AVCaptureStillImageOutput()
        photoOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG]
        if captureSession.canAddOutput(photoOutput) {
            captureSession.addOutput(photoOutput)
        }
    }
    
    func setPreviewLayer() {
        cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        if let cameraView = cameraView {
            cameraPreviewLayer.frame = cameraView.bounds
        }
        cameraPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        cameraView.layer.addSublayer(cameraPreviewLayer)
        cameraPreviewLayer.connection?.videoOrientation = UIDevice.current.orientation.avCaptureVideoOrientation
    }
    
    func startRunningCaptureSession() {
        captureSession.startRunning()
    }
    
    func stopRunningCaptureSession() {
        captureSession.stopRunning()
    }
    
}

extension CameraController {

    open func closeCamera() {
        stopRunningCaptureSession()
        dismiss(animated: true, completion: nil)
    }
    
    open func updateLabel(navTitle: String, title: String, subTitle: String) {
        self.navTitleLabel?.text = navTitle
        self.titleLabel?.text = title
        self.subLabel?.text = subTitle
    }

}

extension UIDeviceOrientation {
    var avCaptureVideoOrientation: AVCaptureVideoOrientation {
        switch self {
        case .portrait:
            return AVCaptureVideoOrientation.portrait
        case .landscapeRight:
            return AVCaptureVideoOrientation.landscapeLeft
        case .landscapeLeft:
            return AVCaptureVideoOrientation.landscapeRight
        case .portraitUpsideDown:
            return AVCaptureVideoOrientation.portraitUpsideDown
        default:
            return AVCaptureVideoOrientation.portrait
        }
    }
}

extension UILabel {
    open func setLabel (sizeFont: CGFloat, color: UIColor) {
        self.textAlignment = .left
        self.font =  UIFont(name: self.font.fontName, size: sizeFont)!
        self.textColor = color
        self.textAlignment = NSTextAlignment.center
        self.backgroundColor = UIColor.clear
    }
}

// fix image orientation
extension UIImage {
    func fixedOrientation() -> UIImage? {
        guard imageOrientation != UIImageOrientation.up else {
            return self.copy() as? UIImage
        }
        
        guard let cgImage = self.cgImage else {
            return nil
        }
        
        guard let colorSpace = cgImage.colorSpace, let ctx = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {
            return nil
        }
        
        var transform: CGAffineTransform = CGAffineTransform.identity
        switch imageOrientation {
        case .down, .downMirrored:
            transform = transform.translatedBy(x: size.width, y: size.height)
            transform = transform.rotated(by: CGFloat.pi)
            break
        case .left, .leftMirrored:
            transform = transform.translatedBy(x: size.width, y: 0)
            transform = transform.rotated(by: CGFloat.pi / 2.0)
            break
        case .right, .rightMirrored:
            transform = transform.translatedBy(x: 0, y: size.height)
            transform = transform.rotated(by: CGFloat.pi / -2.0)
            break
        case .up, .upMirrored:
            break
        }
        
        switch imageOrientation {
        case .upMirrored, .downMirrored:
            transform.translatedBy(x: size.width, y: 0)
            transform.scaledBy(x: -1, y: 1)
            break
        case .leftMirrored, .rightMirrored:
            transform.translatedBy(x: size.height, y: 0)
            transform.scaledBy(x: -1, y: 1)
            break
        case .up, .down, .left, .right:
            break
        }
        
        ctx.concatenate(transform)
        
        switch imageOrientation {
        case .left, .right, .leftMirrored, .rightMirrored:
            ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.height, height: size.width))
            break
        default:
            ctx.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
            break
        }
        
        guard let newCGImage = ctx.makeImage() else {
            return nil
        }
        
        return UIImage.init(cgImage: newCGImage, scale: 1, orientation: .up)
    }
}
